<!-- 作者：麻番薯了
链接：https://zhuanlan.zhihu.com/p/1973712720198120636
来源：知乎
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。 -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue3 图片放大镜效果</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>
    body {
      padding-top: 20px;
    }
    .container {
      max-width: 1200px;
      margin: 0 auto;
    }
    header {
      text-align: center;
      margin-bottom: 30px;
    }
    h1 {
      color: #2c3e50;
      margin-bottom: 10px;
      font-size: 2.2rem;
    }
    .magnifier-app {
      background: white;
      border-radius: 12px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
      padding: 25px;
      margin-bottom: 30px;
    }
    .config-info {
      text-align: center;
      margin-bottom: 25px;
      padding: 15px;
      background: #f8f9fa;
      border-radius: 8px;
      color: #495057;
    }
    .magnifier-container {
      display: flex;
      flex-wrap: wrap;
      gap: 30px;
      justify-content: center;
    }
    .image-section {
      flex: 1;
      min-width: 300px;
    }
    .original-image-container {
      position: relative;
      cursor: crosshair;
      border-radius: 8px;
      overflow: hidden;
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
    }
    .original-image-container img {
      display: block;
      width: 100%;
      height: auto;
    }
    .zoom-lens {
      position: absolute;
      border: 2px solid white;
      background-color: rgba(52, 152, 219, 0.2);
      box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
      pointer-events: none;
      z-index: 10;
    }
    .zoomed-section {
      flex: 1;
      min-width: 300px;
    }
    .zoomed-image-container {
      border-radius: 8px;
      overflow: hidden;
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
      background: #f8f9fa;
      height: 400px;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
    }
    .zoomed-image {
      width: 100%;
      height: 100%;
      background-repeat: no-repeat;
      image-rendering: -webkit-optimize-contrast;
      image-rendering: crisp-edges;
    }
    .placeholder {
      color: #7f8c8d;
      text-align: center;
      padding: 20px;
    }
    .instructions {
      text-align: center;
      margin-top: 20px;
      color: #7f8c8d;
      font-style: italic;
    }
    .status {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 20px;
      margin-top: 15px;
      font-size: 0.9rem;
      color: #7f8c8d;
    }
    .debug-info {
      background: #f8f9fa;
      padding: 15px;
      border-radius: 8px;
      margin-top: 20px;
      font-family: monospace;
      font-size: 0.85rem;
    }
    .pixel-grid {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-image: 
        linear-gradient(rgba(0,0,0,0.1) 1px, transparent 1px),
        linear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px);
      background-size: 10px 10px;
      pointer-events: none;
      opacity: 0.3;
    }
    
    @media (max-width: 768px) {
      .magnifier-container {
        flex-direction: column;
      }
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <header>
        <h1>Vue3 图片放大镜效果</h1>
      </header>
      
      <div class="magnifier-app">
        <div class="config-info">
          <p>当前配置：镜片大小 {{ lensSize }}px | 放大区域 {{ zoomedSize }}px | 放大倍数 {{ zoomLevel }}x</p>
        </div>
        
        <div class="magnifier-container">
          <div class="image-section">
            <div class="original-image-container" 
                 @mousemove="handleMouseMove" 
                 @mouseleave="isVisible = false"
                 @mouseenter="isVisible = true">
              <img 
                ref="originalImage" 
                src="https://picsum.photos/600/400" 
                alt="Original Image" 
                @load="handleImageLoad"
              />
              <div 
                class="zoom-lens" 
                :style="{
                  width: lensSize + 'px',
                  height: lensSize + 'px',
                  left: lensPosition.x + 'px',
                  top: lensPosition.y + 'px',
                  display: isVisible && imageLoaded ? 'block' : 'none'
                }"
              ></div>
            </div>
          </div>
          
          <div class="zoomed-section">
            <div 
              class="zoomed-image-container" 
              :style="{
                width: zoomedSize + 'px',
                height: zoomedSize + 'px'
              }"
            >
              <div v-if="!isVisible || !imageLoaded" class="placeholder">
                <p>将鼠标悬停在左侧图片上查看放大效果</p>
              </div>
              <div 
                v-else
                class="zoomed-image" 
                :style="getZoomedImageStyle()"
              ></div>
              <div class="pixel-grid" v-if="isVisible && imageLoaded"></div>
            </div>
          </div>
        </div>
        
        <div class="status">
          <div>图片原始尺寸: {{ originalWidth }} × {{ originalHeight }}px</div>
          <div>图片显示尺寸: {{ displayWidth }} × {{ displayHeight }}px</div>
          <div>放大镜位置: X:{{ Math.round(lensPosition.x * 1000) / 1000 }}, Y:{{ Math.round(lensPosition.y * 1000) / 1000 }}</div>
        </div>
        
        <div class="debug-info" v-if="imageLoaded">
          <div>比例因子: X={{ scaleX.toFixed(8) }}, Y={{ scaleY.toFixed(8) }}</div>
          <div>原始图片位置: X={{ Math.round(originalX * 1000) / 1000 }}, Y={{ Math.round(originalY * 1000) / 1000 }}</div>
          <div>背景位置: X:{{ Math.round(backgroundPosition.x * 1000) / 1000 }}, Y:{{ Math.round(backgroundPosition.y * 1000) / 1000 }}</div>
          <div>计算精度: {{ (calculationAccuracy * 100).toFixed(6) }}%</div>
          <div>像素偏差: X:{{ Math.abs(pixelDeviation.x).toFixed(3) }}px, Y:{{ Math.abs(pixelDeviation.y).toFixed(3) }}px</div>
        </div>
      </div>
    </div>
  </div>

  <script>
    const { createApp, ref, computed } = Vue;
    
    createApp({
      setup() {
        const originalImage = ref(null);
        const originalWidth = ref(0);
        const originalHeight = ref(0);
        const displayWidth = ref(0);
        const displayHeight = ref(0);
        const lensPosition = ref({ x: 0, y: 0 });
        const isVisible = ref(false);
        const imageLoaded = ref(false);
        const imageUrl = ref('https://picsum.photos/600/400');
        
        // 使用最精准的默认参数
        const lensSize = ref(150);
        const zoomedSize = ref(400);
        const zoomLevel = ref(2);

        // 计算比例因子 - 使用超高精度计算
        const scaleX = computed(() => {
          if (!imageLoaded.value) return 1;
          const scale = originalWidth.value / displayWidth.value;
          return scale;
        });
        
        const scaleY = computed(() => {
          if (!imageLoaded.value) return 1;
          const scale = originalHeight.value / displayHeight.value;
          return scale;
        });

        // 计算原始图片上的精确位置 - 超高精度版本
        const originalX = computed(() => {
          if (!imageLoaded.value) return 0;
          const lensCenterX = lensPosition.value.x + lensSize.value / 2;
          const pos = lensCenterX * scaleX.value;
          return Math.max(0, Math.min(pos, originalWidth.value));
        });
        
        const originalY = computed(() => {
          if (!imageLoaded.value) return 0;
          const lensCenterY = lensPosition.value.y + lensSize.value / 2;
          const pos = lensCenterY * scaleY.value;
          return Math.max(0, Math.min(pos, originalHeight.value));
        });

        // 像素级偏差计算
        const pixelDeviation = computed(() => {
          if (!imageLoaded.value) return { x: 0, y: 0 };
          
          // 计算理论上的完美位置
          const perfectBgX = originalX.value * zoomLevel.value - zoomedSize.value / 2;
          const perfectBgY = originalY.value * zoomLevel.value - zoomedSize.value / 2;
          
          return {
            x: backgroundPosition.value.x - perfectBgX,
            y: backgroundPosition.value.y - perfectBgY
          };
        });

        // 计算精度评估 - 更严格的评估标准
        const calculationAccuracy = computed(() => {
          if (!imageLoaded.value) return 0;
          
          const maxDeviation = Math.max(zoomedSize.value * 0.01, 2); // 允许1%或2像素的偏差
          const xAccuracy = Math.max(0, 1 - Math.abs(pixelDeviation.value.x) / maxDeviation);
          const yAccuracy = Math.max(0, 1 - Math.abs(pixelDeviation.value.y) / maxDeviation);
          
          return (xAccuracy + yAccuracy) / 2;
        });

        // 超精准背景位置计算算法
        const backgroundPosition = computed(() => {
          if (!imageLoaded.value) return { x: 0, y: 0 };
          
          // 核心算法：确保像素级精确对应
          const targetCenterX = originalX.value * zoomLevel.value;
          const targetCenterY = originalY.value * zoomLevel.value;
          
          // 计算背景位置，使放大区域中心精确显示目标位置
          let bgX = targetCenterX - zoomedSize.value / 2;
          let bgY = targetCenterY - zoomedSize.value / 2;
          
          // 精确的边界处理
          const maxBgX = Math.max(0, originalWidth.value * zoomLevel.value - zoomedSize.value);
          const maxBgY = Math.max(0, originalHeight.value * zoomLevel.value - zoomedSize.value);
          
          // 使用更精确的边界检查
          bgX = Math.max(0, Math.min(bgX, maxBgX));
          bgY = Math.max(0, Math.min(bgY, maxBgY));
          
          // 强制像素对齐 - 消除亚像素渲染问题
          bgX = Math.round(bgX * 1000) / 1000;
          bgY = Math.round(bgY * 1000) / 1000;
          
          return { x: bgX, y: bgY };
        });

        const handleImageLoad = () => {
          originalWidth.value = originalImage.value.naturalWidth;
          originalHeight.value = originalImage.value.naturalHeight;
          displayWidth.value = originalImage.value.clientWidth;
          displayHeight.value = originalImage.value.clientHeight;
          imageLoaded.value = true;
          
          console.log('=== 超高精度图片加载信息 ===');
          console.log('原始尺寸:', `${originalWidth.value}x${originalHeight.value}`);
          console.log('显示尺寸:', `${displayWidth.value}x${displayHeight.value}`);
          console.log('比例因子:', `X=${scaleX.value.toFixed(8)}, Y=${scaleY.value.toFixed(8)}`);
        };

        const handleMouseMove = (e) => {
          if (!originalImage.value || !imageLoaded.value) return;
          
          const rect = originalImage.value.getBoundingClientRect();
          
          // 超高精度的鼠标位置计算
          const mouseX = e.clientX - rect.left;
          const mouseY = e.clientY - rect.top;
          
          // 计算放大镜位置（中心对齐）
          let x = mouseX - lensSize.value / 2;
          let y = mouseY - lensSize.value / 2;
          
          // 精确的边界限制
          const maxX = Math.max(0, displayWidth.value - lensSize.value);
          const maxY = Math.max(0, displayHeight.value - lensSize.value);
          
          x = Math.max(0, Math.min(x, maxX));
          y = Math.max(0, Math.min(y, maxY));
          
          // 使用更高精度的数值
          lensPosition.value = { 
            x: Math.round(x * 1000) / 1000, 
            y: Math.round(y * 1000) / 1000 
          };
        };

        const getZoomedImageStyle = () => {
          const bgSize = `${originalWidth.value * zoomLevel.value}px ${originalHeight.value * zoomLevel.value}px`;
          const bgPosition = `-${backgroundPosition.value.x}px -${backgroundPosition.value.y}px`;
          
          return {
            backgroundImage: `url(${imageUrl.value})`,
            backgroundSize: bgSize,
            backgroundPosition: bgPosition,
            transform: `translateZ(0)`, // 硬件加速
            backgroundOrigin: 'border-box'
          };
        };

        return {
          originalImage,
          originalWidth,
          originalHeight,
          displayWidth,
          displayHeight,
          lensPosition,
          backgroundPosition,
          isVisible,
          imageLoaded,
          imageUrl,
          lensSize,
          zoomedSize,
          zoomLevel,
          scaleX,
          scaleY,
          originalX,
          originalY,
          calculationAccuracy,
          pixelDeviation,
          handleImageLoad,
          handleMouseMove,
          getZoomedImageStyle
        };
      }
    }).mount('#app');
  </script>
</body>
</html>